from datetime import datetime, timezone, timedelta

import jwt
import uvicorn
from fastapi import FastAPI, Cookie, Header, Response, Path, HTTPException, status, Request, Depends
from fastapi.responses import JSONResponse
from fastapi.openapi.docs import get_swagger_ui_html
from fastapi.security import OAuth2PasswordRequestForm, OAuth2PasswordBearer
from pydantic import BaseModel, Field
from starlette.staticfiles import StaticFiles



# 创建 FastAPI 应用实例，并把默认的/docs请求禁用
# debug表示是否开启调试模式，默认是False
app = FastAPI(docs_url=None,debug=True)
app.mount("/static", StaticFiles(directory="../static"), name="static")    # 静态文件

@app.get("/docs", include_in_schema=False)
def custom_swagger_ui():
    return get_swagger_ui_html(
        openapi_url = "/openapi.json",
        title = "Swagger UI for FastAPI",
        swagger_js_url="/static/swagger-ui-bundle.min.js",
        swagger_css_url="/static/swagger-ui.min.css"
    )

# 常量
SECURITY_KEY = "W$8pL5qR2sK9vF4jH7gT3mX6cB1nZ0yU!@#dQwE%rA*SxCiO&uVlP-oYbN"
ALGORITHM = "HS256"

# 这个类是用来最终返回使用的
class Token(BaseModel):
    access_token: str
    token_type: str

# 验证用户名和密码
def validate_user(username:str,password:str):
    if username == "jack" and password == "123456":
        return username
    else:
        return None

# 定义登录API
@app.post("/token")
# 这里我们把login_form设置为Depends()后，当请求来了之后会把表单数据转交给OAuth2PasswordRequestForm这个类
async def login(login_form:OAuth2PasswordRequestForm = Depends()):
    # 调用用户验证
    username = validate_user(login_form.username,login_form.password)
    if not username:
        # 这里不存在（验证不通过），在抛异常时同时协带上指定的头信息
        raise HTTPException(status_code=401,detail="用户名或密码错误!",headers={
            "WWW-Authenticate":"Bearer",
            "X-Error":"Invalid username or password"
        })
    # 指定token的过期时间
    token_expires = datetime.now(timezone.utc) + timedelta(minutes=60)
    # 等加密数据
    token_data = {
        "username": username,
        "exp": token_expires
    }
    # 生成token
    token = jwt.encode(token_data,SECURITY_KEY,algorithm=ALGORITHM)
    return Token(access_token=token,token_type="bearer")

# 这里的参数tokenUrl指定了登录的地址，当用户没有登录的时候会提示用户去登录
oauth2_schema = OAuth2PasswordBearer(tokenUrl="/token")


# 这里的Depends(oauth2_schema)会自动从header中提取token
def get_current_username(token:str = Depends(oauth2_schema)):
    unauth_exp = HTTPException(status_code=401,detail="Invalid token",headers={
        "WWW-Authenticate":"Bearer",
        "X-Error":"Invalid token"
    })
    try:
        username = None
        token_data = jwt.decode(token,SECURITY_KEY,ALGORITHM)
        if token_data:
            username = token_data.get("username", None)
    except Exception as e:
        raise unauth_exp

    if not username:
        raise unauth_exp

    return username


# 在访问资源时需要进行token验证
# 方式一：在路由url的注解中使用dependencies指定验证函数，这种方式的话只能是知道验证是否通过，不能使用它的数据
# @app.get("/items",dependencies=[])
# 方式二：在路由函数中定义一个参数，使用Depends()修饰
@app.get("/items")
async def get_items(username:str = Depends(get_current_username)):
    return {"current_user":username}


if __name__ == '__main__':
    uvicorn.run("main:app", host="127.0.0.1", port=8011, reload=True)